// Copyright 2012 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include <windows.h>
#include <array>
#include <string>

#include "context_wgl.h"
#include <common/log.h>

// from wglext.h
#ifndef WGL_ARB_pbuffer
#define WGL_ARB_pbuffer 1
DECLARE_HANDLE(HPBUFFERARB);
#define WGL_DRAW_TO_PBUFFER_ARB 0x202D
#define WGL_MAX_PBUFFER_PIXELS_ARB 0x202E
#define WGL_MAX_PBUFFER_WIDTH_ARB 0x202F
#define WGL_MAX_PBUFFER_HEIGHT_ARB 0x2030
#define WGL_PBUFFER_LARGEST_ARB 0x2033
#define WGL_PBUFFER_WIDTH_ARB 0x2034
#define WGL_PBUFFER_HEIGHT_ARB 0x2035
#define WGL_PBUFFER_LOST_ARB 0x2036
typedef HPBUFFERARB(WINAPI* PFNWGLCREATEPBUFFERARBPROC)(HDC hDC, int iPixelFormat, int iWidth,
                                                        int iHeight, const int* piAttribList);
typedef HDC(WINAPI* PFNWGLGETPBUFFERDCARBPROC)(HPBUFFERARB hPbuffer);
typedef int(WINAPI* PFNWGLRELEASEPBUFFERDCARBPROC)(HPBUFFERARB hPbuffer, HDC hDC);
typedef BOOL(WINAPI* PFNWGLDESTROYPBUFFERARBPROC)(HPBUFFERARB hPbuffer);
typedef BOOL(WINAPI* PFNWGLQUERYPBUFFERARBPROC)(HPBUFFERARB hPbuffer, int iAttribute, int* piValue);
#endif /* WGL_ARB_pbuffer */

#ifndef WGL_ARB_pixel_format
#define WGL_ARB_pixel_format 1
#define WGL_NUMBER_PIXEL_FORMATS_ARB 0x2000
#define WGL_DRAW_TO_WINDOW_ARB 0x2001
#define WGL_DRAW_TO_BITMAP_ARB 0x2002
#define WGL_ACCELERATION_ARB 0x2003
#define WGL_NEED_PALETTE_ARB 0x2004
#define WGL_NEED_SYSTEM_PALETTE_ARB 0x2005
#define WGL_SWAP_LAYER_BUFFERS_ARB 0x2006
#define WGL_SWAP_METHOD_ARB 0x2007
#define WGL_NUMBER_OVERLAYS_ARB 0x2008
#define WGL_NUMBER_UNDERLAYS_ARB 0x2009
#define WGL_TRANSPARENT_ARB 0x200A
#define WGL_TRANSPARENT_RED_VALUE_ARB 0x2037
#define WGL_TRANSPARENT_GREEN_VALUE_ARB 0x2038
#define WGL_TRANSPARENT_BLUE_VALUE_ARB 0x2039
#define WGL_TRANSPARENT_ALPHA_VALUE_ARB 0x203A
#define WGL_TRANSPARENT_INDEX_VALUE_ARB 0x203B
#define WGL_SHARE_DEPTH_ARB 0x200C
#define WGL_SHARE_STENCIL_ARB 0x200D
#define WGL_SHARE_ACCUM_ARB 0x200E
#define WGL_SUPPORT_GDI_ARB 0x200F
#define WGL_SUPPORT_OPENGL_ARB 0x2010
#define WGL_DOUBLE_BUFFER_ARB 0x2011
#define WGL_STEREO_ARB 0x2012
#define WGL_PIXEL_TYPE_ARB 0x2013
#define WGL_COLOR_BITS_ARB 0x2014
#define WGL_RED_BITS_ARB 0x2015
#define WGL_RED_SHIFT_ARB 0x2016
#define WGL_GREEN_BITS_ARB 0x2017
#define WGL_GREEN_SHIFT_ARB 0x2018
#define WGL_BLUE_BITS_ARB 0x2019
#define WGL_BLUE_SHIFT_ARB 0x201A
#define WGL_ALPHA_BITS_ARB 0x201B
#define WGL_ALPHA_SHIFT_ARB 0x201C
#define WGL_ACCUM_BITS_ARB 0x201D
#define WGL_ACCUM_RED_BITS_ARB 0x201E
#define WGL_ACCUM_GREEN_BITS_ARB 0x201F
#define WGL_ACCUM_BLUE_BITS_ARB 0x2020
#define WGL_ACCUM_ALPHA_BITS_ARB 0x2021
#define WGL_DEPTH_BITS_ARB 0x2022
#define WGL_STENCIL_BITS_ARB 0x2023
#define WGL_AUX_BUFFERS_ARB 0x2024
#define WGL_NO_ACCELERATION_ARB 0x2025
#define WGL_GENERIC_ACCELERATION_ARB 0x2026
#define WGL_FULL_ACCELERATION_ARB 0x2027
#define WGL_SWAP_EXCHANGE_ARB 0x2028
#define WGL_SWAP_COPY_ARB 0x2029
#define WGL_SWAP_UNDEFINED_ARB 0x202A
#define WGL_TYPE_RGBA_ARB 0x202B
#define WGL_TYPE_COLORINDEX_ARB 0x202C
typedef BOOL(WINAPI* PFNWGLGETPIXELFORMATATTRIBIVARBPROC)(HDC hdc, int iPixelFormat,
                                                          int iLayerPlane, UINT nAttributes,
                                                          const int* piAttributes, int* piValues);
typedef BOOL(WINAPI* PFNWGLGETPIXELFORMATATTRIBFVARBPROC)(HDC hdc, int iPixelFormat,
                                                          int iLayerPlane, UINT nAttributes,
                                                          const int* piAttributes, FLOAT* pfValues);
typedef BOOL(WINAPI* PFNWGLCHOOSEPIXELFORMATARBPROC)(HDC hdc, const int* piAttribIList,
                                                     const FLOAT* pfAttribFList, UINT nMaxFormats,
                                                     int* piFormats, UINT* nNumFormats);
#endif /* WGL_ARB_pixel_format */

#ifndef WGL_ARB_create_context
#define WGL_ARB_create_context 1
#define WGL_CONTEXT_DEBUG_BIT_ARB 0x00000001
#define WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB 0x00000002
#define WGL_CONTEXT_MAJOR_VERSION_ARB 0x2091
#define WGL_CONTEXT_MINOR_VERSION_ARB 0x2092
#define WGL_CONTEXT_LAYER_PLANE_ARB 0x2093
#define WGL_CONTEXT_FLAGS_ARB 0x2094
#define ERROR_INVALID_VERSION_ARB 0x2095
typedef HGLRC(WINAPI* PFNWGLCREATECONTEXTATTRIBSARBPROC)(HDC hDC, HGLRC hShareContext,
                                                         const int* attribList);
#endif /* WGL_ARB_create_context */

#ifndef WGL_ARB_create_context_profile
#define WGL_ARB_create_context_profile 1
#define WGL_CONTEXT_PROFILE_MASK_ARB 0x9126
#define WGL_CONTEXT_CORE_PROFILE_BIT_ARB 0x00000001
#define WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB 0x00000002
#define ERROR_INVALID_PROFILE_ARB 0x2096
#endif /* WGL_ARB_create_context_profile */

#ifndef WGL_EXT_swap_control
#define WGL_EXT_swap_control 1
typedef BOOL(WINAPI* PFNWGLSWAPINTERVALEXTPROC)(int interval);
typedef int(WINAPI* PFNWGLGETSWAPINTERVALEXTPROC)(void);
#endif /* WGL_EXT_swap_control */

namespace eka2l1::drivers::graphics {
    // Persistent pointers
    static PFNWGLSWAPINTERVALEXTPROC wglSwapIntervalEXT = nullptr;
    static PFNWGLCREATECONTEXTATTRIBSARBPROC wglCreateContextAttribsARB = nullptr;
    static PFNWGLCHOOSEPIXELFORMATARBPROC wglChoosePixelFormatARB = nullptr;
    static PFNWGLCREATEPBUFFERARBPROC wglCreatePbufferARB = nullptr;
    static PFNWGLGETPBUFFERDCARBPROC wglGetPbufferDCARB = nullptr;
    static PFNWGLRELEASEPBUFFERDCARBPROC wglReleasePbufferDCARB = nullptr;
    static PFNWGLDESTROYPBUFFERARBPROC wglDestroyPbufferARB = nullptr;

    static void load_wgl_extensions() {
        wglSwapIntervalEXT =
            reinterpret_cast<PFNWGLSWAPINTERVALEXTPROC>(wglGetProcAddress("wglSwapIntervalEXT"));
        wglCreateContextAttribsARB = reinterpret_cast<PFNWGLCREATECONTEXTATTRIBSARBPROC>(
            wglGetProcAddress("wglCreateContextAttribsARB"));
        wglChoosePixelFormatARB = reinterpret_cast<PFNWGLCHOOSEPIXELFORMATARBPROC>(
            wglGetProcAddress("wglChoosePixelFormatARB"));
        wglCreatePbufferARB =
            reinterpret_cast<PFNWGLCREATEPBUFFERARBPROC>(wglGetProcAddress("wglCreatePbufferARB"));
        wglGetPbufferDCARB =
            reinterpret_cast<PFNWGLGETPBUFFERDCARBPROC>(wglGetProcAddress("wglGetPbufferDCARB"));
        wglReleasePbufferDCARB =
            reinterpret_cast<PFNWGLRELEASEPBUFFERDCARBPROC>(wglGetProcAddress("wglReleasePbufferDCARB"));
        wglDestroyPbufferARB =
            reinterpret_cast<PFNWGLDESTROYPBUFFERARBPROC>(wglGetProcAddress("wglGetPbufferDCARB"));
    }

    static void clear_wgl_extension_pointers() {
        wglSwapIntervalEXT = nullptr;
        wglCreateContextAttribsARB = nullptr;
        wglChoosePixelFormatARB = nullptr;
        wglCreatePbufferARB = nullptr;
        wglGetPbufferDCARB = nullptr;
        wglReleasePbufferDCARB = nullptr;
        wglDestroyPbufferARB = nullptr;
    }

    gl_context_wgl::gl_context_wgl(const drivers::window_system_info& wsi, bool stereo, bool core) {
        if (!wsi.render_surface) {
            LOG_ERROR(DRIVER_GRAPHICS, "Render surface handle is null!");
            return;
        }

        RECT window_rect = {};
        m_window_handle = reinterpret_cast<HWND>(wsi.render_surface);
        if (!GetClientRect(m_window_handle, &window_rect)) {
            LOG_ERROR(DRIVER_GRAPHICS, "Fail to get client rect!");
            return;
        }

        // Clear extension function pointers before creating the first context.
        clear_wgl_extension_pointers();

        // Control window size and picture scaling
        int twidth = window_rect.right - window_rect.left;
        int theight = window_rect.bottom - window_rect.top;

        m_backbuffer_width = twidth;
        m_backbuffer_height = theight;

        const DWORD stereo_flag = stereo ? PFD_STEREO : 0;

        // clang-format off
        static const PIXELFORMATDESCRIPTOR pfd = {
            sizeof(PIXELFORMATDESCRIPTOR),  // Size Of This Pixel Format Descriptor
            1,                              // Version Number
            PFD_DRAW_TO_WINDOW |            // Format Must Support Window
                PFD_SUPPORT_OPENGL |        // Format Must Support OpenGL
                PFD_DOUBLEBUFFER |          // Must Support Double Buffering
                stereo_flag,                // Could Support Quad Buffering
            PFD_TYPE_RGBA,                  // Request An RGBA Format
            32,                             // Select Our Color Depth
            0,
            0, 0, 0, 0, 0,   // Color Bits Ignored
            0,               // 8bit Alpha Buffer
            0,               // Shift Bit Ignored
            0,               // No Accumulation Buffer
            0, 0, 0, 0,      // Accumulation Bits Ignored
            0,               // 0Bit Z-Buffer (Depth Buffer)
            0,               // 0bit Stencil Buffer
            0,               // No Auxiliary Buffer
            PFD_MAIN_PLANE,  // Main Drawing Layer
            0,               // Reserved
            0, 0, 0          // Layer Masks Ignored
        };
        // clang-format on

        m_dc = GetDC(m_window_handle);
        if (!m_dc) {
            LOG_ERROR(DRIVER_GRAPHICS, "(1) Can't create an OpenGL Device context. Fail.");
            return;
        }

        const int pixel_format = ChoosePixelFormat(m_dc, &pfd);
        if (pixel_format == 0) {
            LOG_ERROR(DRIVER_GRAPHICS, "(2) Can't find a suitable PixelFormat.");
            return;
        }

        if (!SetPixelFormat(m_dc, pixel_format, &pfd)) {
            LOG_ERROR(DRIVER_GRAPHICS, "(3) Can't set the PixelFormat.");
            return;
        }

        m_rc = wglCreateContext(m_dc);
        if (!m_rc) {
            LOG_ERROR(DRIVER_GRAPHICS, "(4) Can't create an OpenGL rendering context.");
            return;
        }

        // WGL only supports desktop GL, for now.
        m_opengl_mode = mode::opengl;

        if (core) {
            // Make the fallback context current, temporarily.
            // This is because we need an active context to use wglCreateContextAttribsARB.
            if (!wglMakeCurrent(m_dc, m_rc)) {
                LOG_ERROR(DRIVER_GRAPHICS, "(5) Can't make dummy WGL context current.");
                return;
            }

            // Load WGL extension function pointers.
            load_wgl_extensions();

            // Attempt creating a core context.
            HGLRC core_context = create_core_context(m_dc, nullptr);

            // Switch out the temporary context before continuing, regardless of whether we got a core
            // context. If we didn't get a core context, the caller expects that the context is not current.
            if (!wglMakeCurrent(m_dc, nullptr)) {
                LOG_ERROR(DRIVER_GRAPHICS, "(6) Failed to switch out temporary context");
                return;
            }

            // If we got a core context, destroy and replace the temporary context.
            if (core_context) {
                wglDeleteContext(m_rc);
                m_rc = core_context;
            } else {
                LOG_WARN(DRIVER_GRAPHICS, "Failed to create a core OpenGL context. Using fallback context.");
            }
        }

        make_current();
    }

    gl_context_wgl::~gl_context_wgl() {
        if (m_rc) {
            if (wglGetCurrentContext() == m_rc && !wglMakeCurrent(m_dc, nullptr))
                LOG_ERROR(DRIVER_GRAPHICS, "Could not release drawing context.");

            
            if (!wglDeleteContext(m_rc))
                LOG_ERROR(DRIVER_GRAPHICS, "Attempt to release rendering context failed.");

            m_rc = nullptr;
        }

        if (m_dc) {
            if (m_pbuffer_handle) {
                wglReleasePbufferDCARB(static_cast<HPBUFFERARB>(m_pbuffer_handle), m_dc);
                m_dc = nullptr;

                wglDestroyPbufferARB(static_cast<HPBUFFERARB>(m_pbuffer_handle));
                m_pbuffer_handle = nullptr;
            } else {
                if (!ReleaseDC(m_window_handle, m_dc))
                    LOG_ERROR(DRIVER_GRAPHICS, "Attempt to release device context failed.");

                m_dc = nullptr;
            }
        }
    }

    bool gl_context_wgl::is_headless() const {
        return !m_window_handle;
    }

    void gl_context_wgl::set_swap_interval(int interval) {
        if (wglSwapIntervalEXT) {
            wglSwapIntervalEXT(interval);
        } else {
            LOG_ERROR(DRIVER_GRAPHICS, "No support for SwapInterval (framerate clamped to monitor refresh rate).");
        }
    }

    void gl_context_wgl::swap_buffers() {
        SwapBuffers(m_dc);
    }

    std::unique_ptr<gl_context> gl_context_wgl::create_shared_context() {
        // WGL does not support surfaceless contexts, so we use a 1x1 pbuffer instead.
        HANDLE pbuffer;
        HDC dc;

        if (!create_pbuffer(m_dc, 1, 1, &pbuffer, &dc))
            return nullptr;

        HGLRC rc = create_core_context(dc, m_rc);
        if (!rc) {
            wglReleasePbufferDCARB(static_cast<HPBUFFERARB>(pbuffer), dc);
            wglDestroyPbufferARB(static_cast<HPBUFFERARB>(pbuffer));
            return nullptr;
        }

        std::unique_ptr<gl_context_wgl> context = std::make_unique<gl_context_wgl>();
        context->m_pbuffer_handle = pbuffer;
        context->m_dc = dc;
        context->m_rc = rc;
        context->m_opengl_mode = m_opengl_mode;
        context->m_is_shared = true;

        return context;
    }

    HGLRC gl_context_wgl::create_core_context(HDC dc, HGLRC share_context)
    {
        if (!wglCreateContextAttribsARB) {
            LOG_WARN(DRIVER_GRAPHICS, "Missing WGL_ARB_create_context extension");
            return nullptr;
        }

        for (const auto& version : s_desktop_opengl_versions) {
            // Construct list of attributes. Prefer a forward-compatible, core context.
            std::array<int, 5 * 2> attribs = {WGL_CONTEXT_PROFILE_MASK_ARB,
                                            WGL_CONTEXT_CORE_PROFILE_BIT_ARB,
        #ifdef _DEBUG
                                            WGL_CONTEXT_FLAGS_ARB,
                                            WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB |
                                                WGL_CONTEXT_DEBUG_BIT_ARB,
        #else
                                            WGL_CONTEXT_FLAGS_ARB,
                                            WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB,
        #endif
                                            WGL_CONTEXT_MAJOR_VERSION_ARB,
                                            version.first,
                                            WGL_CONTEXT_MINOR_VERSION_ARB,
                                            version.second,
                                            0,
                                            0};

            // Attempt creating this context.
            HGLRC core_context = wglCreateContextAttribsARB(dc, share_context, attribs.data());
            if (core_context) {
                LOG_INFO(DRIVER_GRAPHICS, "WGL: Created a GL {}.{} core context", version.first, version.second);
                return core_context;
            }
        }

        LOG_ERROR(DRIVER_GRAPHICS, "Unable to create a core OpenGL context of an acceptable version");
        return nullptr;
    }

    bool gl_context_wgl::create_pbuffer(HDC onscreen_dc, int width, int height, HANDLE* pbuffer_handle, HDC* pbuffer_dc) {
        if (!wglChoosePixelFormatARB || !wglCreatePbufferARB || !wglGetPbufferDCARB || !wglReleasePbufferDCARB || !wglDestroyPbufferARB) {
            LOG_ERROR(DRIVER_GRAPHICS, "Missing WGL_ARB_pbuffer extension");
            return false;
        }

        static constexpr std::array<int, 7 * 2> pf_iattribs = {
            WGL_DRAW_TO_PBUFFER_ARB,
            1,  // Pixel format compatible with pbuffer rendering
            WGL_RED_BITS_ARB,
            0,
            WGL_GREEN_BITS_ARB,
            0,
            WGL_BLUE_BITS_ARB,
            0,
            WGL_DEPTH_BITS_ARB,
            0,
            WGL_STENCIL_BITS_ARB,
            0,
            0,
            0};

        static constexpr std::array<float, 1 * 2> pf_fattribs = {};

        int pixel_format;
        UINT num_pixel_formats;
        if (!wglChoosePixelFormatARB(onscreen_dc, pf_iattribs.data(), pf_fattribs.data(), 1, &pixel_format, &num_pixel_formats)) {
            LOG_ERROR(DRIVER_GRAPHICS, "Failed to obtain a compatible pbuffer pixel format");
            return false;
        }

        static constexpr std::array<int, 1 * 2> pb_attribs = {};

        HPBUFFERARB pbuffer = wglCreatePbufferARB(onscreen_dc, pixel_format, width, height, pb_attribs.data());
        if (!pbuffer) {
            LOG_ERROR(DRIVER_GRAPHICS,"Failed to create pbuffer");
            return false;
        }

        HDC dc = wglGetPbufferDCARB(pbuffer);
        if (!dc) {
            LOG_ERROR(DRIVER_GRAPHICS,"Failed to get drawing context from pbuffer");
            wglDestroyPbufferARB(pbuffer);
            return false;
        }

        *pbuffer_handle = pbuffer;
        *pbuffer_dc = dc;
        return true;
    }

    bool gl_context_wgl::make_current() {
        return wglMakeCurrent(m_dc, m_rc) == TRUE;
    }

    bool gl_context_wgl::clear_current() {
        return wglMakeCurrent(m_dc, nullptr) == TRUE;
    }

    // Update window width, size and etc. Called from Render.cpp
    void gl_context_wgl::update(const std::uint32_t new_width, const std::uint32_t new_height) {
        RECT rcWindow;
        GetClientRect(m_window_handle, &rcWindow);

        // Get the new window width and height
        m_backbuffer_width = rcWindow.right - rcWindow.left;
        m_backbuffer_height = rcWindow.bottom - rcWindow.top;
    }
}
